// Copyright Gary Evans 2006-2007.

using System;
using System.Collections.Generic;
using System.Text;

using Tao.OpenGl;

using Taumuon.Jabuka.Physics;

namespace Taumuon.Jabuka.MainApp
{
    /// <summary>
    /// Holds a list of IDrawable objects, and draws them using
    ///     OpenGL calls.
    /// Usage:
    ///   Construct with a list of IDrawable objects,
    ///    and rigid bodies which will be integrated.
    ///   Call InitGl before making any calls.
    ///   Call SetView before doing any drawing, and when the
    ///       size of the viewport changes.
    ///   Call RenderScene to draw the objects to the screen.
    ///   Call UpdateLocations to update the object locations.
    /// </summary>
    public class World
    {
        #region Member Variables

        private IScene scene = null;

        #endregion Member Variables

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="scene">The <see cref="IScene"/> describes the objects in the world.</param>
        /// <exception cref="ArgumentNullException"/>
        public World(IScene scene)
        {
            if (null == scene)
            {
                throw new ArgumentNullException("scene");
            }
            this.scene = scene;
        }

        /// <summary>
        /// Draws a frame.
        /// </summary>
        internal void RenderScene()
        {
            // Clear Screen And Depth Buffer
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT |
                Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glPushMatrix();

            // do our drawing here

            // Perform a translation to compensate for our projection parameters.
            Gl.glTranslated(0.0, 0.0, -80.0);

            if (scene.CameraRotation != null && scene.CameraRotation.Length == 4)
            {
                Gl.glRotated(scene.CameraRotation[0], scene.CameraRotation[1],
                    scene.CameraRotation[2], scene.CameraRotation[3]);
            }

            foreach (IDrawable drawableObject in scene.DrawableObjects)
            {
                drawableObject.Draw();
            }

            Gl.glPopMatrix();
            Gl.glFlush();
        }

        internal void SetView(int height, int width)
        {
            // Set viewport to window dimensions.
            Gl.glViewport(0, 0, width, height);

            // Reset projection matrix stack
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            //const float nRange = 80.0f;

            // Prevent a divide by zero
            if (height == 0)
            {
                height = 1;
            }

            // Establish clipping volume (left, right, bottom,
            // top, near, far)
            if (width <= height)
            {
                //Gl.glOrtho(-nRange, nRange, -nRange * height / width,
                //          nRange * height / width, -nRange, nRange);
                // http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
                Glu.gluPerspective(100.0, ((double)width / height), 20.0, 10000.0);
            }
            else
            {
                //Gl.glOrtho(-nRange * width / height, nRange * width / height,
                //    -nRange, nRange, -nRange, nRange);
                Glu.gluPerspective(100.0, ((double)height / width), 20.0, 10000.0);
            }

            // reset modelview matrix stack
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        internal void InitGl()
        {
            Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            Gl.glEnable(Gl.GL_CULL_FACE);
            Gl.glCullFace(Gl.GL_BACK);
            Gl.glDepthMask(Gl.GL_FALSE);
            Gl.glShadeModel(Gl.GL_SMOOTH);
            Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);


            // From http://www.sjbaker.org/steve/omniv/opengl_lighting.html

            float[] ambient = { 0.2f, 0.2f, 0.2f, 1.0f };
            float[] diffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
            float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
            float[] position = { 20.0f, 20.0f, -80.0f, 1.0f };
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_AMBIENT, ambient);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_DIFFUSE, diffuse);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, specular);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, position);

            Gl.glEnable(Gl.GL_COLOR_MATERIAL);
            Gl.glEnable(Gl.GL_LIGHT0);
            Gl.glEnable(Gl.GL_LIGHTING);
        }

        internal void UpdateLocations(double increment)
        {
            scene.Update(increment);
        }

        internal void ResetScene()
        {
            scene.ResetScene();
        }
    }
}
